/*
This code provided under the terms of the Id Software 
LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the
GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of 
LICENSE_ID, please contact Id Software immediately at info@idsoftware.com.

All changes and additions to the original source which have been developed by
other contributors (see CONTRIBUTORS) are provided under the terms of the
license the contributors choose (see LICENSE), to the extent permitted by the
LICENSE_ID. If you did not receive a copy of the contributor license,
please contact the GtkRadiant maintainers at info@gtkradiant.com immediately.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <float.h>

#include "mathlib.h"

const aabb_t g_aabb_null = {
  { 0, 0, 0, },
  { -FLT_MAX, -FLT_MAX, -FLT_MAX, },
};

void aabb_construct_for_vec3(aabb_t *aabb, const vec3_t min, const vec3_t max)
{
  VectorMid(min, max, aabb->origin);
  VectorSubtract(max, aabb->origin, aabb->extents);
}

void aabb_clear(aabb_t *aabb)
{
  VectorCopy(g_aabb_null.origin, aabb->origin);
  VectorCopy(g_aabb_null.extents, aabb->extents);
}

void aabb_extend_by_point(aabb_t *aabb, const vec3_t point)
{
#if 1
  int i;
  vec_t min, max, displacement;
  for(i=0; i<3; i++)
  {
    displacement = point[i] - aabb->origin[i];
    if(fabs(displacement) > aabb->extents[i])
    {
      if(aabb->extents[i] < 0) // degenerate
      {
        min = max = point[i];
      }
      else if(displacement > 0)
      {
        min = aabb->origin[i] - aabb->extents[i];
        max = aabb->origin[i] + displacement;
      }
      else
      {
        max = aabb->origin[i] + aabb->extents[i];
        min = aabb->origin[i] + displacement;
      }
      aabb->origin[i] = (min + max) * 0.5f;
      aabb->extents[i] = max - aabb->origin[i];
    }
  }
#else
  unsigned int i;
  for(i=0; i<3; ++i)
  {
    if(aabb->extents[i] < 0) // degenerate
    {
      aabb->origin[i] = point[i];
      aabb->extents[i] = 0;
    }
    else
    {
      vec_t displacement = point[i] - aabb->origin[i];
      vec_t increment = (vec_t)fabs(displacement) - aabb->extents[i];
      if(increment > 0)
      {
        increment *= (vec_t)((displacement > 0) ? 0.5 : -0.5);
        aabb->extents[i] += increment;
        aabb->origin[i] += increment;
      }
    }
  }
#endif
}

void aabb_extend_by_aabb(aabb_t *aabb, const aabb_t *aabb_src)
{
  int i;
  vec_t min, max, displacement, difference;
  for(i=0; i<3; i++)
  {
    displacement = aabb_src->origin[i] - aabb->origin[i];
    difference = aabb_src->extents[i] - aabb->extents[i];
    if(aabb->extents[i] < 0
      || difference >= fabs(displacement))
    {
      // 2nd contains 1st
      aabb->extents[i] = aabb_src->extents[i];
      aabb->origin[i] = aabb_src->origin[i];
    }
    else if(aabb_src->extents[i] < 0
      || -difference >= fabs(displacement))
    {
      // 1st contains 2nd
      continue;
    }
    else
    {
      // not contained
      if(displacement > 0)
      {
        min = aabb->origin[i] - aabb->extents[i];
        max = aabb_src->origin[i] + aabb_src->extents[i];
      }
      else
      {
        min = aabb_src->origin[i] - aabb_src->extents[i];
        max = aabb->origin[i] + aabb->extents[i];
      }
      aabb->origin[i] = (min + max) * 0.5f;
      aabb->extents[i] = max - aabb->origin[i];
    }
  }
}

void aabb_extend_by_vec3(aabb_t *aabb, vec3_t extension)
{
  VectorAdd(aabb->extents, extension, aabb->extents);
}

int aabb_test_point(const aabb_t *aabb, const vec3_t point)
{
  int i;
  for(i=0; i<3; i++)
    if(fabs(point[i] - aabb->origin[i]) >= aabb->extents[i])
      return 0;
  return 1;
}

int aabb_test_aabb(const aabb_t *aabb, const aabb_t *aabb_src)
{
  int i;
  for (i=0; i<3; i++)
    if ( fabs(aabb_src->origin[i] - aabb->origin[i]) > (fabs(aabb->extents[i]) + fabs(aabb_src->extents[i])) )
      return 0;
  return 1;
}

int aabb_test_plane(const aabb_t *aabb, const float *plane)
{
  float fDist, fIntersect;

  // calc distance of origin from plane
  fDist = DotProduct(plane, aabb->origin) + plane[3];
  
   // calc extents distance relative to plane normal
  fIntersect = (vec_t)(fabs(plane[0] * aabb->extents[0]) + fabs(plane[1] * aabb->extents[1]) + fabs(plane[2] * aabb->extents[2]));
  // accept if origin is less than or equal to this distance
  if (fabs(fDist) < fIntersect) return 1; // partially inside
  else if (fDist < 0) return 2; // totally inside
  return 0; // totally outside
}

/* 
Fast Ray-Box Intersection
by Andrew Woo
from "Graphics Gems", Academic Press, 1990
*/

#define NUMDIM	3
#define RIGHT	0
#define LEFT	1
#define MIDDLE	2

int aabb_intersect_ray(const aabb_t *aabb, const ray_t *ray, vec3_t intersection)
{
	int inside = 1;
	char quadrant[NUMDIM];
	register int i;
	int whichPlane;
	double maxT[NUMDIM];
	double candidatePlane[NUMDIM];
  
  const float *origin = ray->origin;
  const float *direction = ray->direction;

	/* Find candidate planes; this loop can be avoided if
   	rays cast all from the eye(assume perpsective view) */
	for (i=0; i<NUMDIM; i++)
  {
		if(origin[i] < (aabb->origin[i] - aabb->extents[i]))
    {
			quadrant[i] = LEFT;
			candidatePlane[i] = (aabb->origin[i] - aabb->extents[i]);
			inside = 0;
		}
    else if (origin[i] > (aabb->origin[i] + aabb->extents[i]))
    {
			quadrant[i] = RIGHT;
			candidatePlane[i] = (aabb->origin[i] + aabb->extents[i]);
			inside = 0;
		}
    else
    {
			quadrant[i] = MIDDLE;
    }
  }

	/* Ray origin inside bounding box */
	if(inside == 1)
  {
		VectorCopy(ray->origin, intersection);
		return 1;
	}


	/* Calculate T distances to candidate planes */
	for (i = 0; i < NUMDIM; i++)
  {
		if (quadrant[i] != MIDDLE && direction[i] !=0.)
			maxT[i] = (candidatePlane[i] - origin[i]) / direction[i];
		else
			maxT[i] = -1.;
  }

	/* Get largest of the maxT's for final choice of intersection */
	whichPlane = 0;
	for (i = 1; i < NUMDIM; i++)
		if (maxT[whichPlane] < maxT[i])
			whichPlane = i;

	/* Check final candidate actually inside box */
	if (maxT[whichPlane] < 0.)
    return 0;
	for (i = 0; i < NUMDIM; i++)
  {
		if (whichPlane != i)
    {
			intersection[i] = (vec_t)(origin[i] + maxT[whichPlane] * direction[i]);
			if (fabs(intersection[i] - aabb->origin[i]) > aabb->extents[i])
				return 0;
		}
    else
    {
			intersection[i] = (vec_t)candidatePlane[i];
		}
  }

	return 1;				/* ray hits box */
}

int aabb_test_ray(const aabb_t* aabb, const ray_t* ray)
{
 vec3_t displacement, ray_absolute;
 vec_t f;
 
 displacement[0] = ray->origin[0] - aabb->origin[0];
 if(fabs(displacement[0]) > aabb->extents[0] && displacement[0] * ray->direction[0] >= 0.0f)
   return 0;
 
 displacement[1] = ray->origin[1] - aabb->origin[1];
 if(fabs(displacement[1]) > aabb->extents[1] && displacement[1] * ray->direction[1] >= 0.0f)
   return 0;
 
 displacement[2] = ray->origin[2] - aabb->origin[2];
 if(fabs(displacement[2]) > aabb->extents[2] && displacement[2] * ray->direction[2] >= 0.0f)
   return 0;
 
 ray_absolute[0] = (float)fabs(ray->direction[0]);
 ray_absolute[1] = (float)fabs(ray->direction[1]);
 ray_absolute[2] = (float)fabs(ray->direction[2]);

 f = ray->direction[1] * displacement[2] - ray->direction[2] * displacement[1];
 if((float)fabs(f) > aabb->extents[1] * ray_absolute[2] + aabb->extents[2] * ray_absolute[1])
   return 0;

 f = ray->direction[2] * displacement[0] - ray->direction[0] * displacement[2];
 if((float)fabs(f) > aabb->extents[0] * ray_absolute[2] + aabb->extents[2] * ray_absolute[0])
   return 0;

 f = ray->direction[0] * displacement[1] - ray->direction[1] * displacement[0];
 if((float)fabs(f) > aabb->extents[0] * ray_absolute[1] + aabb->extents[1] * ray_absolute[0])
   return 0;
 
 return 1;
}

void aabb_orthogonal_transform(aabb_t* dst, const aabb_t* src, const m4x4_t transform)
{
  VectorCopy(src->origin, dst->origin);
  m4x4_transform_point(transform, dst->origin);

  dst->extents[0] = (vec_t)(fabs(transform[0]  * src->extents[0])
                          + fabs(transform[4]  * src->extents[1])
                          + fabs(transform[8]  * src->extents[2]));
  dst->extents[1] = (vec_t)(fabs(transform[1]  * src->extents[0])
                          + fabs(transform[5]  * src->extents[1])
                          + fabs(transform[9]  * src->extents[2]));
  dst->extents[2] = (vec_t)(fabs(transform[2]  * src->extents[0])
                          + fabs(transform[6]  * src->extents[1])
                          + fabs(transform[10] * src->extents[2]));
}

void aabb_for_bbox(aabb_t *aabb, const bbox_t *bbox)
{
	int i;
	vec3_t temp[3];

  VectorCopy(bbox->aabb.origin, aabb->origin);

	// calculate the AABB extents in local coord space from the OBB extents and axes
	VectorScale(bbox->axes[0], bbox->aabb.extents[0], temp[0]);
	VectorScale(bbox->axes[1], bbox->aabb.extents[1], temp[1]);
	VectorScale(bbox->axes[2], bbox->aabb.extents[2], temp[2]);
	for(i=0;i<3;i++) aabb->extents[i] = (vec_t)(fabs(temp[0][i]) + fabs(temp[1][i]) + fabs(temp[2][i]));
}

void aabb_for_area(aabb_t *aabb, vec3_t area_tl, vec3_t area_br, int axis)
{
  aabb_clear(aabb);
  aabb->extents[axis] = FLT_MAX;
  aabb_extend_by_point(aabb, area_tl);
  aabb_extend_by_point(aabb, area_br);
}

int aabb_oriented_intersect_plane(const aabb_t *aabb, const m4x4_t transform, const vec_t* plane)
{
  vec_t fDist, fIntersect;

  // calc distance of origin from plane
  fDist = DotProduct(plane, aabb->origin) + plane[3];

  // calc extents distance relative to plane normal
  fIntersect = (vec_t)(fabs(aabb->extents[0] * DotProduct(plane, transform))
    + fabs(aabb->extents[1] * DotProduct(plane, transform+4))
    + fabs(aabb->extents[2] * DotProduct(plane, transform+8)));
  // accept if origin is less than this distance
  if (fabs(fDist) < fIntersect) return 1; // partially inside
  else if (fDist < 0) return 2; // totally inside
  return 0; // totally outside
}

void aabb_corners(const aabb_t* aabb, vec3_t corners[8])
{
  vec3_t min, max;
  VectorSubtract(aabb->origin, aabb->extents, min);
  VectorAdd(aabb->origin, aabb->extents, max);
  VectorSet(corners[0], min[0], max[1], max[2]);
  VectorSet(corners[1], max[0], max[1], max[2]);
  VectorSet(corners[2], max[0], min[1], max[2]);
  VectorSet(corners[3], min[0], min[1], max[2]);
  VectorSet(corners[4], min[0], max[1], min[2]);
  VectorSet(corners[5], max[0], max[1], min[2]);
  VectorSet(corners[6], max[0], min[1], min[2]);
  VectorSet(corners[7], min[0], min[1], min[2]);
}


void bbox_update_radius(bbox_t *bbox)
{
  bbox->radius = VectorLength(bbox->aabb.extents);
}

void aabb_for_transformed_aabb(aabb_t* dst, const aabb_t* src, const m4x4_t transform)
{
  if(src->extents[0] < 0
    || src->extents[1] < 0
    || src->extents[2] < 0)
  {
    aabb_clear(dst);
    return;
  }

  VectorCopy(src->origin, dst->origin);
  m4x4_transform_point(transform, dst->origin);

  dst->extents[0] = (vec_t)(fabs(transform[0]  * src->extents[0])
                          + fabs(transform[4]  * src->extents[1])
                          + fabs(transform[8]  * src->extents[2]));
  dst->extents[1] = (vec_t)(fabs(transform[1]  * src->extents[0])
                          + fabs(transform[5]  * src->extents[1])
                          + fabs(transform[9]  * src->extents[2]));
  dst->extents[2] = (vec_t)(fabs(transform[2]  * src->extents[0])
                          + fabs(transform[6]  * src->extents[1])
                          + fabs(transform[10] * src->extents[2]));
}

void bbox_for_oriented_aabb(bbox_t *bbox, const aabb_t *aabb, const m4x4_t matrix, const vec3_t euler, const vec3_t scale)
{
	double rad[3];
	double pi_180 = Q_PI / 180;
  double A, B, C, D, E, F, AD, BD;
  
	VectorCopy(aabb->origin, bbox->aabb.origin);
	
  m4x4_transform_point(matrix, bbox->aabb.origin);

	bbox->aabb.extents[0] = aabb->extents[0] * scale[0];
	bbox->aabb.extents[1] = aabb->extents[1] * scale[1];
	bbox->aabb.extents[2] = aabb->extents[2] * scale[2];

  rad[0] = euler[0] * pi_180;
	rad[1] = euler[1] * pi_180;
	rad[2] = euler[2] * pi_180;

  A       = cos(rad[0]);
  B       = sin(rad[0]);
  C       = cos(rad[1]);
  D       = sin(rad[1]);
  E       = cos(rad[2]);
  F       = sin(rad[2]);

  AD      =   A * -D;
  BD      =   B * -D;

	bbox->axes[0][0] = (vec_t)(C*E);
	bbox->axes[0][1] = (vec_t)(-BD*E + A*F);
	bbox->axes[0][2] = (vec_t)(AD*E + B*F);
	bbox->axes[1][0] = (vec_t)(-C*F);
	bbox->axes[1][1] = (vec_t)(BD*F + A*E);
	bbox->axes[1][2] = (vec_t)(-AD*F + B*E);
	bbox->axes[2][0] = (vec_t)D;
	bbox->axes[2][1] = (vec_t)(-B*C);
	bbox->axes[2][2] = (vec_t)(A*C);

  bbox_update_radius(bbox);
}

int bbox_intersect_plane(const bbox_t *bbox, const vec_t* plane)
{
  vec_t fDist, fIntersect;

  // calc distance of origin from plane
  fDist = DotProduct(plane, bbox->aabb.origin) + plane[3];

	// trivial accept/reject using bounding sphere
	if (fabs(fDist) > bbox->radius)
	{
		if (fDist < 0)
			return 2; // totally inside
		else
			return 0; // totally outside
	}

  // calc extents distance relative to plane normal
  fIntersect = (vec_t)(fabs(bbox->aabb.extents[0] * DotProduct(plane, bbox->axes[0]))
             + fabs(bbox->aabb.extents[1] * DotProduct(plane, bbox->axes[1]))
             + fabs(bbox->aabb.extents[2] * DotProduct(plane, bbox->axes[2])));
  // accept if origin is less than this distance
  if (fabs(fDist) < fIntersect) return 1; // partially inside
  else if (fDist < 0) return 2; // totally inside
  return 0; // totally outside
}
